/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2016年12月26日
 * V4.0
 */
package com.jphenix.driver.cluster;

import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.lang.SString;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.lang.IResultRow;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 集群服务器信息类
 * 
 * com.jphenix.driver.cluster.ServerInfoVO
 * 
 * 2018-05-24 增加了服务器启动时间
 * 2018-06-11 增加了目标服务器返回错误信息属性
 * 2018-06-28 增加了允许未登记服务器（未在配置文件中配置）访问当前服务器
 * 2018-07-02 为服务器配置信息增加了自定义参数设置和获取方法
 * 2019-01-08 弃用了invalidServer，请看变量invalidServer声明处的注释
 * 2019-07-09 废弃了agentBalance参数，没有用到
 * 2019-07-16 增加了url() 方法，返回可以调用目标服务器的根url
 * 2019-09-27 增加了代理中转相关参数
 * 2019-10-22 新增用于在本地调试模拟复杂网络环境中集群服务器相互调用场景。在配置信息server节点中增加了dis_send，禁止发送心跳包到目标服务器的名字（多个用逗号分割）
 * 2019-12-20 增加了集群配置信息遗漏导致无法正常使用时，显示错误日志
 * 2020-03-30 取消了无用的变量。将新增变量信息放到了toString方法中
 * 2020-06-18 增加了服务器会话主键名信息（在代理调用时会用到）
 * 
 * @author MBG
 * 2016年12月26日
 */
@ClassInfo({"2020-06-18 18:47","集群服务器信息类"})
public class ServerInfoVO implements IResultRow {

	public String  name               = null;  //服务器名（唯一值）
	public String  ip                 = null;  //服务器地址
	public String  url                = null;  //服务器调用URL（不包含虚拟路径）
	public String  context            = null;  //虚拟路径 （其它代理服务需要单独用到虚拟路径，用来判断本地跟目标虚拟路径是否相同，有必要）如果没有虚拟路径，为空（不为/）
	public String  group              = null;  //服务器分组名
	public String  startTime          = null;  //服务器的启动时间
	public String  runTime            = null;  //服务器运行时长
	public String  msg                = null;  //目标服务器返回错误信息
	public String  routeName          = null;  //如果无法直接调用该信息的服务器，可以通过该路由服务器中转调用
	public String  sessionKeyName     = null;  //会话主键名
	public int     routeLevel         = -1;    //经转路由层级（经过多少台服务器代理才调用到目标服务器）未初始化时为-1
	public int     serverIndex        = -1;    //服务器索引   0为主服务器
	                                
	public boolean disabled           = false; //是否禁用
	public boolean local              = false; //是否为当前服务器
	public boolean master             = false; //是否为主服务器
	public boolean unableRoute        = false; //是否无法通过路由方式调用（通常用在两个网段单向通信时，其中一方无法调用另外一方，但是能接收到另外一方的心跳信息）
	
	public boolean triggerFirstSend   = false; //是否执行了首次成功发送心跳 触发事件
	public boolean triggerFristRecive = false; //是否执行了首次成功接收心跳触发事件
	
	//禁止发送的服务器名序列，该属性用于本地测试复杂网络中，多台集群应用相互调用功能
	//模拟无法接收方法：在发送服务器端设置禁止发送到该服务器即可
	protected List<String> disabledSendServerNameList = new ArrayList<String>();
	
	/*
	 * 是否使用了反向代理工具做集群
	 * 
	 * 使用群组中并行服务器集群时，如果前面使用了反向代理工具做负载均衡，
	 * 那么调用服务器只能通过反向代理工具（比如Nginx）访问到集群中某台
	 * 服务器，无法访问到指定服务器。
	 * 
	 * 如果并行集群中其中一台服务器使用了反向触发方式调用前端（通过反向
	 * 代理工具调用并行集群的服务器），前端服务器通过反向代理工具准备获
	 * 获取反向触发数据时，调用的可能并不是发起反向触发的那台服务器
	 * 
	 * 所以需要增加这个标识，由程序判断出事这种网络情况，并且针对这种
	 * 网络情况做处理
	 * 
	 * 通常不赞成使用反向触发方式调用目标服务器，尽量在设计程序时避免
	 * 这种情况。因为反向触发调用的执行效率非常低
	 */
	//取消该变量值，无用
	//public boolean agentBalance     = false;
	
	//很多时候，不同网段的集群配置，外网段服务器调用内网段服务器，
	//但是内网段服务器是无法调用外网段服务器的，所以将是否有效的变量拆成两个
	public boolean validSend        = false; //是否能访问目标服务器
	public boolean validReceive     = false; //目标服务器是否访问当前服务器
	
	//注释原因：目标服务器也是集群内部服务器，但是在启动tomcat过程中，集群功能还没加载好时，对方服务器的心跳请求直接被当前
	//        tomcat返回404，导致目标服务器不再访问当前服务器。
	//public boolean invalidServer    = false; //无效的集群服务器（如果发现目标服务器为无效集群服务器，就不再刷新该服务器状态，不再使用该服务器，避免目标服务器频繁收到心跳请求）
	public boolean anonymous        = false; //本机是否允许未登记（未在配置文件中登记）的服务器加入集群
	public boolean anonymousClient  = false; //是否为未登记的客户端信息
	public long    sendTime         = 0;     //发送请求时间
	public long    receiveTime      = 0;     //接收到请求的时间
	
	public int     invokeCount      = 0;     //被远程调用次数
	public int     invokeClearFlag  = 0;     //重置远程调用次数标识（每次重置后，这个标识的值会发生变化）
	
	private Map<String,String> pMap = null;  //服务器配置信息容器
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ServerInfoVO() {
		super();
	}
	
	/**
	 * 设置服务器配置信息容器
	 * @param pMap 服务器配置信息容器
	 * 2018年7月2日
	 * @author MBG
	 */
	public void setParameterMap(Map<String,String> pMap) {
		if(pMap==null) {
			return;
		}
		this.pMap      = pMap;
		name           = SString.valueOf(pMap.remove("name"));
		ip             = SString.valueOf(pMap.remove("ip"));
    	url            = SString.valueOf(pMap.remove("url"));
    	context        = SString.valueOf(pMap.remove("context"));
    	group          = SString.valueOf(pMap.remove("group"));
    	sessionKeyName = SString.valueOf(pMap.remove("sessionKeyName"));
    	disabled       = SBoolean.valueOf(pMap.remove("disabled"));
    	anonymous      = SBoolean.valueOf(pMap.remove("anonymous"));
    	if(sessionKeyName==null || sessionKeyName.length()<1) {
    		sessionKeyName = "JSESSIONID";
    	}
    	if(ip.length()<1 || url.length()<1) {
    		if(!disabled) {
    			System.err.println("\n\n\n**********************************ERROR**!!! The Cluster Server Config [ip] or [url] is Empty For:["+name+"] Group:["+group+"]  !!!**********************************\n\n\n");
    		}
    		disabled = true;
    	}
    	if("/".equals(context)) {
    		context = "";
    	}
	}
	
	/**
	 * 返回可以调用目标服务器的根URL
	 * @return 可以调用目标服务器的根URL
	 * 2019年7月16日
	 * @author MBG
	 */
	public String url() {
		return url+SString.valueOf(context);
	}

	/**
	 * 获取有效的服务器配置信息容器
	 * @return 有效的服务器配置信息容器
	 * 2018年7月2日
	 * @author MBG
	 */
	public Map<String,String> getParameterMap(){
		if(pMap==null) {
			pMap = new HashMap<String,String>();
		}
		return pMap;
	}
	
	/**
	 * 获取指定的服务器配置信息参数
	 * @param key  参数主键
	 * @return     参数值
	 * 2018年7月2日
	 * @author MBG
	 */
	public String parameter(String key) {
		return SString.valueOf(getParameterMap().get(key));
	}
	
	/**
	 * 获取指定的服务器配置信息参数（布尔值）
	 * @param key  参数主键
	 * @return     参数值
	 * 2018年7月2日
	 * @author MBG
	 */
	public boolean parameterBool(String key) {
		return SBoolean.valueOf(getParameterMap().get(key));
	}
	
	/**
	 * 记录集行接口方法
	 */
	@Override
	public Map<String, String> _getMap() {
		//构建返回值
		HashMap<String,String> reMap = new HashMap<String,String>();
		reMap.put("name",name);
		reMap.put("ip",ip);
		reMap.put("url",url);
		reMap.put("msg",msg);
		reMap.put("context",context);
		reMap.put("group",group);
		reMap.put("sessionKeyName",sessionKeyName);
		reMap.put("master",master?"1":"0");
		reMap.put("disabled",disabled?"1":"0");
		reMap.put("unableRoute",unableRoute?"1":"0");
		reMap.put("local",local?"1":"0");
		reMap.put("validSend",validSend?"1":"0");
		reMap.put("serverIndex",String.valueOf(serverIndex));
		//已弃用，请看变量invalidServer声明处的注释
		//reMap.put("invalidServer",invalidServer?"1":"0");
		reMap.put("validReceive",validReceive?"1":"0");
		reMap.put("sendTime",sendTime==0?"-":SDate.getTs(sendTime));
		reMap.put("receiveTime",receiveTime==0?"-":SDate.getTs(receiveTime));
		reMap.put("invokeCount",String.valueOf(invokeCount));
		reMap.put("invokeClearFlag",String.valueOf(invokeClearFlag));
		reMap.put("startTime",startTime);
		reMap.put("runTime",runTime);
		return reMap;
	}
	
	/**
	 * 覆盖方法
	 */
	@Override
    public String toString() {
		return (new StringBuffer())
				  .append("group:[").append(group).append("]\n")
				  .append("name:[").append(name).append("]\n")
				  .append("ip:[").append(ip).append("]\n")
				  .append("url:[").append(url).append("]\n")
				  .append("context:[").append(context).append("]\n")
				  .append("sessionKeyName:[").append(sessionKeyName).append("]\n")
				  .append("serverIndex:[").append(serverIndex).append("]\n")
				  .append("master:[").append(master).append("]\n")
				  .append("disabled:[").append(disabled).append("]\n")
				  .append("unableRoute:[").append(unableRoute).append("]\n")
				  .append("local:[").append(local).append("]\n")
				  .append("validSend:[").append(validSend).append("]\n")
				  .append("msg:[").append(msg==null?"":msg).append("]\n")
				  .append("validReceive:[").append(validReceive).append("]\n")
				  //已弃用，请看变量invalidServer声明处的注释
				  //.append("invalidServer:[").append(invalidServer).append("]\n")
				  .append("sendTime:[").append(sendTime==0?"-":SDate.getTs(sendTime)).append("]\n")
				  .append("receiveTime:[").append(receiveTime==0?"-":SDate.getTs(receiveTime)).append("]\n")
				  .append("invokeCount:[").append(invokeCount).append("]\n")
				  .append("invokeClearFlag:[").append(invokeClearFlag).append("]\n")
				  .append("startTime:[").append(startTime).append("]\n")
				  .append("runTime:[").append(runTime).append("]\n")
				  .toString();
	}
}
